﻿#include "hzpch.h"
#include "OpenGLFrameBuffer.h"

#include <glad/glad.h>

namespace Hazal
{
    namespace Utils
    {
		static bool IsDepthFormat(EFramebufferTextureFormat format)
		{
			switch (format)
			{
			case EFramebufferTextureFormat::DEPTH24STENCIL8:
				return true;
			default:
				return false;
			}
		}

        static GLenum TextureTarget(bool bIsMultiSample)
        {
            return bIsMultiSample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
        }

        static void CreateTextures(bool bIsMultiSample, uint32_t* pOutId, uint32_t unCount)
        {
            glCreateTextures(TextureTarget(bIsMultiSample), unCount, pOutId);
        }

        static void BindTexture(bool bIsMultiSample, uint32_t unRendererID)
        {
            glBindTexture(TextureTarget(bIsMultiSample), unRendererID);
        }

        static void AttachColorTexture(uint32_t unRendererID, uint32_t unSamples, GLenum internalFormat, GLenum format, uint32_t unWidth, uint32_t unHeight, int nIndex)
        {
            bool bIsMultiSample = unSamples > 1;
            if (bIsMultiSample)
            {
				glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, unSamples, internalFormat, unWidth, unHeight, GL_FALSE);
			}
            else
            {
				glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, unWidth, unHeight, 0, format, GL_UNSIGNED_BYTE, nullptr);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            }

            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + nIndex, TextureTarget(bIsMultiSample), unRendererID, 0);
        }

        static void AttachDepthTexture(uint32_t unRendererID, uint32_t unSample, GLenum format, GLenum attachmentType, uint32_t unWidth, uint32_t unHeight)
        {
            bool bIsMultiSample = unSample > 1;
            if (bIsMultiSample)
            {
                glTextureStorage2DMultisample(unRendererID, unSample, format, unWidth, unHeight, GL_FALSE);
            }
            else
            {
                glTextureStorage2D(unRendererID, 1, format, unWidth, unHeight);
			}

            glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentType, TextureTarget(bIsMultiSample), unRendererID, 0);
        }

        static GLenum ConvertFBFormatToGLFormat(EFramebufferTextureFormat format)
        {
            switch (format)
            {
            case EFramebufferTextureFormat::RGBA8:
                return GL_RGBA;
            case EFramebufferTextureFormat::RED_INTEGER:
                return GL_RED_INTEGER;
            default:
                break;
            }

            HZ_CORE_ASSERT(false, "Current Framebuffer foramt is invalid!");
            return GL_NONE;
        }
    }

    COpenGLFrameBuffer::COpenGLFrameBuffer(const SFrameBufferSpecification& spec)
        : m_specification(spec), m_unRendererID(0), m_unDepthAttachment(0)
    {
        for (auto& format : spec.m_attachments.m_vecAttachments)
        {
            if (!Utils::IsDepthFormat(format.m_eFormat))
            {
                m_vecColorAttachmentSpecs.emplace_back(format);
            }
            else
            {
                m_DepthAttachmentSpec = format;
            }
        }

        Invalidate();
    }

    COpenGLFrameBuffer::~COpenGLFrameBuffer()
    {
        glDeleteFramebuffers(1, &m_unRendererID);
		glDeleteTextures(m_vecColorAttachments.size(), m_vecColorAttachments.data());
        glDeleteTextures(1, &m_unDepthAttachment);
    }

    SFrameBufferSpecification& COpenGLFrameBuffer::GetSpecification()
    {
        return m_specification;
    }

    const SFrameBufferSpecification& COpenGLFrameBuffer::GetSpecification() const
    {
        return m_specification;
    }

    uint32_t COpenGLFrameBuffer::GetColorAttachmentRendererID(int32_t nIndex) const
    {
        HZ_CORE_ASSERT(nIndex < m_vecColorAttachments.size(), "Color attachment index overflow!");
        return m_vecColorAttachments[nIndex];
    }

    int COpenGLFrameBuffer::ReadPixel(uint32_t unAttachmentIndex, int x, int y)
    {
        /// 使用时需要进行处于绑定状态
        HZ_CORE_ASSERT(unAttachmentIndex < m_vecColorAttachments.size(), "attachment index can not over {0}", m_vecColorAttachments.size());

        /// glReadBuffer 函数指定颜色缓冲区作为后续 glReadPixels 和 glCopyPixels 命令的源
        glReadBuffer(GL_COLOR_ATTACHMENT0 + unAttachmentIndex);

        /// 只获取一个像素值, 所以width和height参数值均为1
        int nPixelData = -1;
        glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &nPixelData);

        return nPixelData;
    }

    void COpenGLFrameBuffer::ClearAttachment(uint32_t unAttachmentIndex, int nValue) const
    {
		/// 使用时需要进行处于绑定状态
		HZ_CORE_ASSERT(unAttachmentIndex < m_vecColorAttachments.size(), "attachment index can not over {0}", m_vecColorAttachments.size());

        auto& spec = m_vecColorAttachmentSpecs[unAttachmentIndex];

        glClearTexImage(m_vecColorAttachments[unAttachmentIndex], 0, Utils::ConvertFBFormatToGLFormat(spec.m_eFormat), GL_INT, &nValue);
	}

    void COpenGLFrameBuffer::Bind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, m_unRendererID);

        /// 帧缓冲大小改变时, 需要同步修改视口大小
        glViewport(0, 0, m_specification.m_unWidth, m_specification.m_unHeight);
    }

    void COpenGLFrameBuffer::Unbind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void COpenGLFrameBuffer::Resize(uint32_t unWidth, uint32_t unHeight)
    {
        m_specification.m_unWidth = unWidth;
        m_specification.m_unHeight = unHeight;

        Invalidate();
    }

    void COpenGLFrameBuffer::Invalidate()
    {
        /// 如果当前已经创建了帧缓冲, 则删除当前存在的纹理, 并基于新的尺寸创建纹理
        if (m_unRendererID)
        {
            glDeleteFramebuffers(1, &m_unRendererID);
            glDeleteTextures(m_vecColorAttachments.size(), m_vecColorAttachments.data());
            glDeleteTextures(1, &m_unDepthAttachment);

            m_vecColorAttachments.clear();
            m_unDepthAttachment = 0;
        }

        glCreateFramebuffers(1, &m_unRendererID);
        glBindFramebuffer(GL_FRAMEBUFFER, m_unRendererID);

        /// 是否多重采样(MSAA)
        bool bMultiSample = m_specification.m_unSamples > 1;

		/// 创建颜色纹理附件, 并附加到帧缓冲对象中
		if (m_vecColorAttachmentSpecs.size() > 0)
        {
            m_vecColorAttachments.resize(m_vecColorAttachmentSpecs.size());

            Utils::CreateTextures(bMultiSample, m_vecColorAttachments.data(), m_vecColorAttachments.size());

            for (size_t i = 0; i < m_vecColorAttachments.size(); ++i)
            {
                Utils::BindTexture(bMultiSample, m_vecColorAttachments[i]);

                GLenum internalFormat = GL_NONE, format = GL_NONE;
                switch (m_vecColorAttachmentSpecs[i].m_eFormat)
                {
                case EFramebufferTextureFormat::RGBA8:
					Utils::AttachColorTexture(m_vecColorAttachments[i], m_specification.m_unSamples, GL_RGBA8, GL_RGBA, m_specification.m_unWidth, m_specification.m_unHeight, i);
					break;
                case EFramebufferTextureFormat::RED_INTEGER:
					Utils::AttachColorTexture(m_vecColorAttachments[i], m_specification.m_unSamples, GL_R32I, GL_RED_INTEGER, m_specification.m_unWidth, m_specification.m_unHeight, i);
					break;
                default:
                    break;
                }

            }
        }

        /// 创建深度纹理附件
        if (EFramebufferTextureFormat::None != m_DepthAttachmentSpec.m_eFormat)
        {
            Utils::CreateTextures(bMultiSample, &m_unDepthAttachment, 1);
            Utils::BindTexture(bMultiSample, m_unDepthAttachment);

            switch (m_DepthAttachmentSpec.m_eFormat)
            {
            case EFramebufferTextureFormat::DEPTH24STENCIL8:
                Utils::AttachDepthTexture(m_unDepthAttachment, m_specification.m_unSamples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, 
                    m_specification.m_unWidth, m_specification.m_unHeight);
                break;
            }
        }

        if (m_vecColorAttachments.size() > 1)
        {
            HZ_CORE_ASSERT(m_vecColorAttachments.size() <= 4, "");
            GLenum aBuffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 , GL_COLOR_ATTACHMENT2 ,GL_COLOR_ATTACHMENT3 };

            /// 指定要绘制到哪些颜色缓冲区中
            glDrawBuffers(m_vecColorAttachments.size(), aBuffers);
        }
        else if (m_vecColorAttachments.empty())
        {
            glDrawBuffer(GL_NONE);
        }


        /// 检测帧缓冲区完整性
        HZ_CORE_ASSERT(GL_FRAMEBUFFER_COMPLETE == glCheckFramebufferStatus(GL_FRAMEBUFFER), "Frambuffer is incomplete!")

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
}